# -*- coding: utf-8 -*-
"""
数据验证模块

提供数据质量检查和验证功能
"""

import pandas as pd
import numpy as np
from typing import Dict, List, Any, Tuple, Optional
try:
    from ..core.config import Config
except ImportError:
    # 如果相对导入失败，尝试绝对导入
    import sys
    import os
    sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
    from core.config import Config
from .logger import LoggerMixin


class DataValidator(LoggerMixin):
    """数据验证器"""
    
    def __init__(self, config: Optional[Config] = None):
        super().__init__()
        self.config = config or Config()
    
    def validate_dataframe(self, df: pd.DataFrame) -> Dict[str, Any]:
        """验证DataFrame的质量
        
        Args:
            df: 要验证的DataFrame
            
        Returns:
            验证结果字典
        """
        validation_result = {
            'is_valid': True,
            'warnings': [],
            'errors': [],
            'statistics': {},
            'recommendations': []
        }
        
        # 基本统计信息
        validation_result['statistics'] = self._get_basic_statistics(df)
        
        # 检查样本数量
        min_samples = self.config.get('data_validation.min_samples', 10)
        if len(df) < min_samples:
            validation_result['errors'].append(
                f"样本数量过少: {len(df)} < {min_samples}"
            )
            validation_result['is_valid'] = False
        
        # 检查缺失值比例
        missing_ratio = df.isnull().sum().sum() / (df.shape[0] * df.shape[1])
        max_missing_ratio = self.config.get('data_validation.max_missing_ratio', 0.5)
        
        if missing_ratio > max_missing_ratio:
            validation_result['warnings'].append(
                f"缺失值比例过高: {missing_ratio:.2%} > {max_missing_ratio:.2%}"
            )
        
        # 检查数值特征数量
        numeric_cols = df.select_dtypes(include=[np.number]).columns
        min_numeric_features = self.config.get('data_validation.min_numeric_features', 1)
        
        if len(numeric_cols) < min_numeric_features:
            validation_result['errors'].append(
                f"数值特征过少: {len(numeric_cols)} < {min_numeric_features}"
            )
            validation_result['is_valid'] = False
        
        # 检查重复行
        duplicate_count = df.duplicated().sum()
        if duplicate_count > 0:
            validation_result['warnings'].append(
                f"发现 {duplicate_count} 行重复数据"
            )
            validation_result['recommendations'].append("建议删除重复行")
        
        # 检查异常值
        outlier_info = self._detect_outliers(df)
        if outlier_info['total_outliers'] > 0:
            validation_result['warnings'].append(
                f"发现 {outlier_info['total_outliers']} 个异常值"
            )
            validation_result['statistics']['outliers'] = outlier_info
        
        # 检查数据类型一致性
        type_issues = self._check_data_types(df)
        if type_issues:
            validation_result['warnings'].extend(type_issues)
            validation_result['recommendations'].append("建议检查数据类型一致性")
        
        # 检查列名规范性
        column_issues = self._check_column_names(df)
        if column_issues:
            validation_result['warnings'].extend(column_issues)
            validation_result['recommendations'].append("建议规范化列名")
        
        self.log_info(f"数据验证完成: {'通过' if validation_result['is_valid'] else '失败'}")
        
        return validation_result
    
    def _get_basic_statistics(self, df: pd.DataFrame) -> Dict[str, Any]:
        """获取基本统计信息"""
        numeric_cols = df.select_dtypes(include=[np.number]).columns
        categorical_cols = df.select_dtypes(include=['object']).columns
        
        return {
            'shape': df.shape,
            'total_cells': df.shape[0] * df.shape[1],
            'missing_cells': df.isnull().sum().sum(),
            'missing_ratio': df.isnull().sum().sum() / (df.shape[0] * df.shape[1]),
            'numeric_columns': len(numeric_cols),
            'categorical_columns': len(categorical_cols),
            'memory_usage_mb': df.memory_usage(deep=True).sum() / 1024 / 1024,
            'duplicate_rows': df.duplicated().sum()
        }
    
    def _detect_outliers(self, df: pd.DataFrame) -> Dict[str, Any]:
        """检测异常值"""
        numeric_cols = df.select_dtypes(include=[np.number]).columns
        outlier_info = {
            'total_outliers': 0,
            'columns_with_outliers': {},
            'method': self.config.get('analysis.outlier_method', 'iqr')
        }
        
        for col in numeric_cols:
            if self.config.get('analysis.outlier_method') == 'iqr':
                outliers = self._detect_outliers_iqr(df[col])
            else:
                outliers = self._detect_outliers_zscore(df[col])
            
            if len(outliers) > 0:
                outlier_info['columns_with_outliers'][col] = {
                    'count': len(outliers),
                    'percentage': len(outliers) / len(df) * 100,
                    'indices': outliers.tolist()
                }
                outlier_info['total_outliers'] += len(outliers)
        
        return outlier_info
    
    def _detect_outliers_iqr(self, series: pd.Series) -> np.ndarray:
        """使用IQR方法检测异常值"""
        Q1 = series.quantile(0.25)
        Q3 = series.quantile(0.75)
        IQR = Q3 - Q1
        threshold = self.config.get('analysis.outlier_threshold', 1.5)
        
        lower_bound = Q1 - threshold * IQR
        upper_bound = Q3 + threshold * IQR
        
        outliers = series[(series < lower_bound) | (series > upper_bound)]
        return outliers.index.values
    
    def _detect_outliers_zscore(self, series: pd.Series, threshold: float = 3.0) -> np.ndarray:
        """使用Z-score方法检测异常值"""
        z_scores = np.abs((series - series.mean()) / series.std())
        outliers = series[z_scores > threshold]
        return outliers.index.values
    
    def _check_data_types(self, df: pd.DataFrame) -> List[str]:
        """检查数据类型一致性"""
        issues = []
        
        for col in df.columns:
            # 检查是否有混合类型
            if df[col].dtype == 'object':
                # 尝试转换为数值类型
                try:
                    pd.to_numeric(df[col], errors='raise')
                    issues.append(f"列 '{col}' 可能应该是数值类型")
                except (ValueError, TypeError):
                    pass
                
                # 检查是否包含数值字符串
                numeric_strings = df[col].astype(str).str.match(r'^-?\d+\.?\d*$')
                if numeric_strings.any() and not numeric_strings.all():
                    issues.append(f"列 '{col}' 包含混合的数值和文本数据")
        
        return issues
    
    def _check_column_names(self, df: pd.DataFrame) -> List[str]:
        """检查列名规范性"""
        issues = []
        
        for col in df.columns:
            # 检查空格
            if ' ' in col:
                issues.append(f"列名 '{col}' 包含空格")
            
            # 检查特殊字符
            if not col.replace('_', '').replace('-', '').isalnum():
                issues.append(f"列名 '{col}' 包含特殊字符")
            
            # 检查中文字符（可选）
            if any('\u4e00' <= char <= '\u9fff' for char in col):
                # 这里不报告为问题，因为中文列名在某些场景下是合理的
                pass
        
        # 检查重复列名
        if len(df.columns) != len(set(df.columns)):
            issues.append("存在重复的列名")
        
        return issues
    
    def suggest_improvements(self, df: pd.DataFrame) -> List[str]:
        """建议数据改进措施"""
        suggestions = []
        validation_result = self.validate_dataframe(df)
        
        # 基于验证结果生成建议
        if validation_result['statistics']['missing_ratio'] > 0.1:
            suggestions.append("考虑使用更好的缺失值处理策略")
        
        if validation_result['statistics']['duplicate_rows'] > 0:
            suggestions.append("删除重复行以提高数据质量")
        
        if len(df.select_dtypes(include=[np.number]).columns) < 3:
            suggestions.append("增加更多数值特征以提高分析效果")
        
        if len(df) < 100:
            suggestions.append("增加样本量以提高分析结果的可靠性")
        
        return suggestions


def validate_file_format(file_path: str) -> Tuple[bool, str]:
    """验证文件格式
    
    Args:
        file_path: 文件路径
        
    Returns:
        (是否有效, 错误信息)
    """
    if not file_path:
        return False, "文件路径不能为空"
    
    if not file_path.lower().endswith(('.csv', '.xlsx', '.xls')):
        return False, "不支持的文件格式，请使用CSV或Excel文件"
    
    try:
        import os
        if not os.path.exists(file_path):
            return False, "文件不存在"
        
        if os.path.getsize(file_path) == 0:
            return False, "文件为空"
        
        return True, ""
    except Exception as e:
        return False, f"文件验证失败: {str(e)}"